package com.chuangke.file.service.impl;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.chuangke.common.constant.FileConstants;
import com.chuangke.common.constant.Masks;
import com.chuangke.common.event.file.FileUpdateEvent;
import com.chuangke.common.event.file.FileUploadEvent;
import com.chuangke.common.exception.ChuangkeException;
import com.chuangke.common.idgen.IdManager;
import com.chuangke.common.utils.FileUtils;
import com.chuangke.file.dao.FileInfoMapper;
import com.chuangke.file.dao.FileVersionMapper;
import com.chuangke.file.model.FileBrief;
import com.chuangke.file.model.FileChunk;
import com.chuangke.file.model.FileInfo;
import com.chuangke.file.model.FileInfoVersion;
import com.chuangke.file.service.FileDbankService;
import com.chuangke.file.service.FileDownloadHistoryService;
import com.chuangke.file.service.FileInfoService;
import com.chuangke.file.service.FileTransferService;
import com.chuangke.file.util.TikaUtil;
import com.chuangke.fss.FSSClient;
import com.chuangke.fss.model.FSSObject;

import cn.hutool.core.util.StrUtil;

/**
 * @Description
 * @author stm
 * @date 2019年2月24日 下午9:52:09
 */
@Service
public class FileTransferServiceImpl implements FileTransferService {
	
	private static final Logger logger = LoggerFactory.getLogger(FileTransferServiceImpl.class);

	public static final String CACHE_REGIN = "FILE-QUICK-MD5";

	@Autowired
	private ApplicationEventPublisher eventPublisher;

	@Autowired
	private FileInfoMapper fileMapper;
	@Autowired
	private FileVersionMapper fileVersionMapper;
	@Autowired
	private FileInfoService fileInfoService;
	@Autowired
	private FileDownloadHistoryService fileDownloadHistoryService;
	@Autowired
	private FileDbankService fileDbankService;
	@Autowired
	private FSSClient fssClient;

	@Autowired
	private CacheManager cacheManager;

	@Override
	public FileInfo upload(FileChunk fileChunk) {
		if (StrUtil.isBlank(fileChunk.getParentId())) {
			fileChunk.setParentId(FileConstants.FILE_ROOT_PARENT);
		}

		String parentId = ((FileTransferService) AopContext.currentProxy()).mkDirByChunk(fileChunk);

        return ((FileTransferService) AopContext.currentProxy()).upload(fileChunk.getFile(),
				fileChunk.getDbankId(), parentId, fssClient.getDefaultStorageType());
	}

	@Override
	public FileInfo upload(MultipartFile file, String dbankId) {
		return ((FileTransferService) AopContext.currentProxy()).upload(file, dbankId,
				fssClient.getDefaultStorageType());
	}

	@Override
	public FileInfo upload(MultipartFile file, String dbankId, String parentId) {
		return ((FileTransferService) AopContext.currentProxy()).upload(file, dbankId, parentId,
				fssClient.getDefaultStorageType());
	}

	@Transactional(rollbackFor = Throwable.class)
	@Override
	public FileInfo upload(MultipartFile file, String dbankId, String parentId, String storageType) {
		FileInfo fileInfo = createDefaultFileInfo(dbankId, parentId);

		fileInfo = doUpload(file, fileInfo, storageType);

		fileMapper.insert(fileInfo);

		// 将文件放入文件历史表
		FileInfoVersion fileInfoVersion = fileInfo.convertToFileInfoVersion();
		fileVersionMapper.insert(fileInfoVersion);

		eventPublisher.publishEvent(new FileUploadEvent(this, fileInfo));

		return fileInfo;
	}

	@Transactional(rollbackFor = Throwable.class)
	@Override
	public FileInfo reUpload(MultipartFile file, String fileId) {
		if (fileId == null || fileId.isEmpty()) {
			throw new ChuangkeException("请确定需要重新上传的文件");
		}

		FileInfo fileInfo = fileMapper.selectById(fileId);

		// 1.上传文件到文件系统
		fileInfo = doUpload(file, fileInfo, fileInfo.getStorageType());

		// 2.将新的文件放入文件系统表
		fileInfo.setSignId(IdManager.nextId());
		fileInfo.setVersion(fileInfo.getVersion() + 1);
		fileMapper.updateById(fileInfo);

		// 3.首先将原有文件的记录放入文件历史表
		FileInfoVersion fileInfoVersion = fileInfo.convertToFileInfoVersion();
		fileVersionMapper.insert(fileInfoVersion);

		eventPublisher.publishEvent(new FileUploadEvent(this, fileInfo));

		return fileInfo;
	}

	private FileInfo doUpload(MultipartFile file, FileInfo fileInfo, String storageType) {
		fileInfo.setName(file.getOriginalFilename());
		fileInfo.setSuffix(FileUtils.getSuffix(file.getOriginalFilename()));
		fileInfo.setSize(file.getSize());
		fileInfo.setType(FileInfo.TYPE_FILE);

		byte[] fileContent = null;
		try {
			fileContent = file.getBytes();
		} catch (IOException e) {
			throw new ChuangkeException(e);
		}

		fileInfo.setMd5hex(DigestUtils.md5Hex(fileContent));
		fileInfo.setStorage2("files/" + fileInfo.getMd5hex());// 指定存储地址，minio上传用到，minio上传需要提前知道object name
		fileInfo.setContent(fileContent);
		fileInfo.setMimeType(TikaUtil.getMimeType(fileContent));

		uploadToFss(fileContent, fileInfo, storageType);

		fileDbankService.addUsedSpace(fileInfo.getDbankId(), BigInteger.valueOf(fileInfo.getSize()));

		return fileInfo;
	}

	private void uploadToFss(byte[] fileContent, FileInfo fileInfo, String storageType) {
		FSSObject fssObject = fileInfo.convert();
		fssObject.setObjectContent(fileContent);
		fssClient.getFss(storageType).upload(fssObject);
		fileInfo.convert(fssObject);
	}

	@Override
	public String mkDirByChunk(FileChunk fileChunk) {
		String path = fileChunk.getRelativePath();
		String parentId = fileChunk.getParentId();
		if (path.contains("/")) {
			String[] paths = path.split("/");
			for (int i = 0; i < paths.length - 1; i++) {
				FileInfo fileInfo = ((FileTransferService) AopContext.currentProxy()).autoMkDir(fileChunk.getDbankId(),
						parentId, paths[i]);
				parentId = fileInfo.getId();
			}
		}
		return parentId;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
//	@Klock(keys = { "#dbankId" }, lockType = LockType.Fair)
	public FileInfo autoMkDir(String dbankId, String parentId, String path) {
		List<FileInfo> list = fileMapper
				.selectList(new QueryWrapper<FileInfo>().lambda().eq(FileInfo::getDbankId, dbankId)
						.eq(FileInfo::getParentId, parentId).eq(FileInfo::getType, FileInfo.TYPE_DIR)
						.like(FileInfo::getName, path).eq(FileInfo::getDelFlag, Masks.BOOL_NO));

		if (list != null && !list.isEmpty()) {
			return list.get(0);
		}

		FileInfo fileInfo = new FileInfo();
		fileInfo.setDbankId(dbankId);
		fileInfo.setParentId(parentId);
		fileInfo.setName(path);
		fileInfo.setType(FileInfo.TYPE_DIR);
		fileInfo.setDelFlag(Masks.BOOL_NO);
		fileInfo.setIsPublish(Masks.BOOL_NO);
		return fileInfoService.createDir(fileInfo);
	}

	@Override
	public Map<String, byte[]> download(List<String> fileIds) {
//		Map<String, FileInfo> fileMap = new HashMap<>();
//		Map<String, String> fileNameMap = new HashMap<>();
//		List<FileInfo> fileList = getFileWithChildren(fileIds);
//		findAllChildrenFile(fileList, fileMap, fileNameMap);
//
//		List<FileInfo> fileWithoutDirList = new ArrayList<>();
//		Map<String, byte[]> fileByteMap = new HashMap<>();
//		fileMap.values().stream().forEach(file -> {
//			if (FileInfo.TYPE_FILE.equals(file.getType())) {
//				file.setDownloadCount(file.getDownloadCount() + 1);
//				fileWithoutDirList.add(file);
//				FSSObject fssObject = file.convert();
//				if(StrUtil.isBlank(fssObject.getStorageType())) {
//					System.out.println(file);
//				}
//				fileByteMap.put(fileNameMap.get(file.getId()), downloadFile(fssObject));
//			}
//		});
//
//		fileDownloadHistoryService
//				.saveBatch(fileWithoutDirList.stream().map(FileInfo::getId).collect(Collectors.toList()));
//		return fileByteMap;
		return null;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public byte[] download(String fileId, String signId) {
		fileDownloadHistoryService.save(fileId);

		FileInfoVersion fileVersionInfo = fileVersionMapper.selectOne(new QueryWrapper<FileInfoVersion>().lambda()
				.eq(FileInfoVersion::getId, fileId).eq(FileInfoVersion::getSignId, signId));
		if(fileVersionInfo == null) {
			logger.error("未找到文件[fileId:{};signId:{}]",fileId,signId);
			throw new ChuangkeException("未找到文件");
		}
		fileVersionMapper.update(null,
				new UpdateWrapper<FileInfoVersion>().lambda()
						.set(FileInfoVersion::getDownloadCount, fileVersionInfo.getDownloadCount() + 1)
						.eq(FileInfoVersion::getId, fileId).eq(FileInfoVersion::getSignId, signId));
		
		fileMapper.updateDownloadCount(fileId);

		FSSObject fssObject = fileVersionInfo.convert();

		byte[] fileBytes = downloadFromFSS(fssObject);

		eventPublisher.publishEvent(new FileUpdateEvent(this, Collections.singletonList(fileId)));

		return fileBytes;
	}

	private byte[] downloadFromFSS(FSSObject fssObject) {
		return fssClient.getFss(fssObject.getStorageType()).download(fssObject).getObjectContent();
	}

	@Override
	public boolean secondUpload(FileBrief fileBrief) {
		List<FileInfoVersion> list = fileVersionMapper.selectList(
				new QueryWrapper<FileInfoVersion>().lambda().eq(FileInfoVersion::getMd5hex, fileBrief.getFileMd5()));

		// @ TODO 完善并发情况
		if (list == null || list.isEmpty()) {
//			String userId = SecurityUtils.getUserId() ;
			fileBrief = fssClient.getFss(fssClient.getDefaultStorageType()).uploadAppenderFile(fileBrief);

//			SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
//			filter.getExcludes().add("file");
//			cacheChannel.set(CACHE_REGIN, fileBrief.getFileMd5(), JSON.toJSON(fileBrief));
			cacheManager.getCache(CACHE_REGIN).put(fileBrief.getFileMd5(), JSON.toJSON(fileBrief));

			return false;
		}

		FileInfo fileInfo = list.get(0).convertToFileInfo();
		fileInfo.setId(IdManager.nextId());
		fileInfo.setDbankId(fileBrief.getDbankId());
		fileInfo.setIsAllSee(Masks.BOOL_YES);
		fileInfo.setDownloadCount(0);
		fileInfo.setDownloadPeopleCount(0);
		fileInfo.setVersion(1);
		fileInfo.setParentId(fileInfo.getParentId() == null ? "" : fileInfo.getParentId());

		fileMapper.insert(fileInfo);

		return true;
	}

	@Override
	public void uploadChunk(FileChunk fileChunk) {
		String fbCache = (String) cacheManager.getCache(CACHE_REGIN).get(fileChunk.getFileMd5()).get() ;
//		CacheObject fbCache = cacheChannel.get(CACHE_REGIN, fileChunk.getFileMd5());
		if (ObjectUtils.isNull(fbCache)) {
			secondUpload(fileChunk);
		} else {
			FileBrief fb = JSON.parseObject(fbCache.toString(), FileBrief.class);
			fileChunk.setStoreage1(fb.getStoreage1());
			fileChunk.setStoreage2(fb.getStoreage2());
			fssClient.getFss(fssClient.getDefaultStorageType()).appenderFile(fileChunk);
		}
	}

	@Override
	public void mergeFile(FileBrief fileBrief) {
        FileBrief fb = JSON.parseObject((String) cacheManager.getCache(CACHE_REGIN).get(fileBrief.getFileMd5()).get(),
				FileBrief.class);
		fileBrief.setStoreage1(fb.getStoreage1());
		fileBrief.setStoreage2(fb.getStoreage2());
		fssClient.getFss(fssClient.getDefaultStorageType()).mergeFile(fileBrief);
		cacheManager.getCache(CACHE_REGIN).evict(fileBrief.getFileMd5());
//		cacheChannel.evict(CACHE_REGIN, fileBrief.getFileMd5());

		FileInfo fileInfo = new FileInfo();
		fileInfo.setId(IdManager.nextId());
		fileInfo.setDbankId(fileBrief.getDbankId());
		fileInfo.setIsAllSee(Masks.BOOL_YES);
		fileInfo.setDownloadCount(0);
		fileInfo.setDownloadPeopleCount(0);
		fileInfo.setVersion(1);
		fileInfo.setParentId(fileInfo.getParentId() == null ? "" : fileInfo.getParentId());
		fileInfo.setName(fileBrief.getFilename());
		fileInfo.setSuffix(fileBrief.getSuffix());
		fileInfo.setSize(fileBrief.getTotalSize());
		fileInfo.setType(FileInfo.TYPE_FILE);

		fileMapper.insert(fileInfo);
	}

	@Override
	public byte[] images(String storageType, String thumStorage1, String thumStorage2) {
		FSSObject fssObject = new FSSObject();
		fssObject.setStorage1(thumStorage1);
		fssObject.setStorage2(thumStorage2);

		byte[] imgBytes = null;

		if (StrUtil.isBlank(thumStorage1) && StrUtil.isBlank(thumStorage2)) {
			return getDefaultThum();
		}

		imgBytes = fssClient.getFss(storageType).download(fssObject).getObjectContent();
		if (ArrayUtils.isEmpty(imgBytes)) {
			imgBytes = getDefaultThum();
		}
//		return imgBytes == null ? "" : Base64.getEncoder().encodeToString(imgBytes);
		return imgBytes;
	}
	
	@Override
	public byte[] justDownload(String storageType, String storage1, String storage2) {
		FSSObject fssObject = new FSSObject();
		fssObject.setStorage1(storage1);
		fssObject.setStorage2(storage2);

		byte[] imgBytes = null;

		imgBytes = fssClient.getFss(storageType).download(fssObject).getObjectContent();
		if (ArrayUtils.isEmpty(imgBytes)) {
			imgBytes = getDefaultThum();
		}
		return imgBytes;
	}

	private FileInfo createDefaultFileInfo(String dbankId, String parentId) {
		FileInfo fileInfo = new FileInfo();
		fileInfo.setId(IdManager.nextId());
		fileInfo.setSignId(IdManager.nextId());
		fileInfo.setDbankId(dbankId);
		fileInfo.setIsAllSee(Masks.BOOL_YES);
		fileInfo.setDownloadCount(0);
		fileInfo.setDownloadPeopleCount(0);
		fileInfo.setVersion(1);
		fileInfo.setParentId(parentId);
		fileInfo.setIsPublish(Masks.BOOL_NO);
		return fileInfo;
	}

	private byte[] getDefaultThum() {
		try {
			File file = ResourceUtils.getFile("classpath:document_default_thum.png");
			return FileUtils.readFileToByteArray(file);
		} catch (Exception e) {
		}
		return null;
	}

}
